<?php

namespace app\checkin\controller;

use app\checkin\model\CheckinRecord as RecordModel;
use app\checkin\model\PointsRecord as PointsModel;
use app\checkin\model\Member as MemberModel;
use app\checkin\model\MemberStore as MemberStoreModel;
use app\checkin\model\Setting as SettingModel;
use think\admin\helper\QueryHelper;
use think\facade\Db;

/**
 * 打卡记录管理
 * @class Record
 * @package app\checkin\controller
 */
class Record extends Base
{
    /**
     * 打卡记录列表
     * @auth true
     * @menu true
     */
    public function index()
    {
        $this->title = '打卡记录';
        RecordModel::mQuery()->layTable(function () {
            $this->assign('statusMap', RecordModel::$statusMap);
            $this->assign('stores', $this->getAccessibleStores());
        }, function (QueryHelper $query) {
            // 会员姓名搜索（精准匹配）
            $memberName = trim($this->request->get('member_name', ''));
            if ($memberName !== '') {
                $query->whereIn('member_id', function($q) use ($memberName) {
                    $q->table(MemberModel::mk()->getTable())
                      ->where('name', $memberName)
                      ->field('id');
                });
            }

            // 卡号、状态、门店、时间范围搜索
            $query->like('card_no')->equal('status,store_id')->dateBetween('start_time');

            // 数据权限：非总账号显示本店记录 + 与本店有关系会员的所有跨店记录
            if (!$this->isMaster) {
                $relTable = \app\checkin\model\MemberStore::mk()->getTable();
                $sid = $this->storeId;
                $query->where(function ($q) use ($relTable, $sid) {
                    $q->where(['store_id' => $sid])
                      ->whereOrRaw("member_id IN (SELECT member_id FROM {$relTable} WHERE store_id = :sid)", ['sid' => $sid]);
                });
            }
            // 关联展示
            $query->with(['member' => static function($q){ $q->field('id,name,phone'); }, 'store' => static function($q){ $q->field('id,name'); }]);
            // 倒序排列（最新的在前面）
            $query->order('id desc');
        });
    }

    /**
     * 结束并结算积分
     * @auth true
     */
    public function finish()
    {
        $id = $this->request->post('id', '') ?: $this->request->get('id', '');
        if ($id === '') $this->error('缺少记录ID！');
        $ids = array_values(array_filter(array_map('intval', is_array($id) ? $id : explode(',', strval($id)))));
        if (empty($ids)) $this->error('记录ID格式不正确！');

        // 读取规则配置（全局优先，后续可扩展为按门店）
        $unitMinutes   = intval(SettingModel::getSetting('unit_minutes', 0, 10)); // 每多少分钟=1分
        $roundingMode  = strval(SettingModel::getSetting('rounding', 0, 'floor')); // floor|round|ceil
        $timeoutMinute = intval(SettingModel::getSetting('timeout_minutes', 0, 0)); // 超时阈值(分钟)，0为不启用
        if ($unitMinutes <= 0) $unitMinutes = 10;

        foreach ($ids as $rid) {
            Db::transaction(function () use ($rid, $unitMinutes, $roundingMode, $timeoutMinute) {
                // 读取记录并校验
                $record = RecordModel::mk()->where(['id' => $rid])->lock(true)->findOrEmpty();
                if ($record->isEmpty()) throw new \Exception('记录不存在：'.$rid);
                $storeId = intval($record->getAttr('store_id'));
                if (!$this->checkStorePermission($storeId)) throw new \Exception('无权限操作该门店的记录');
                if (intval($record->getAttr('status')) !== 3) throw new \Exception('仅可结束进行中的记录');
                $start = $record->getAttr('start_time');
                if (empty($start)) throw new \Exception('记录开始时间异常');

                // 结束并计算时长
                $end = date('Y-m-d H:i:s');
                $duration = max(0, intval((strtotime($end) - strtotime($start)) / 60));

                // 超时作废逻辑
                if ($timeoutMinute > 0 && $duration > $timeoutMinute) {
                    $record->save([
                        'end_time' => $end,
                        'duration_minutes' => $duration,
                        'points' => 0,
                        'status' => 2,
                        'invalid_reason' => '超时作废',
                    ]);
                    return; // 作废不产生积分
                }

                // 计算积分
                $score = 0;

                // 1) 若配置了“时间段积分”，按时间段积分规则累计（points_per_hour）
                $slotsJson = strval(SettingModel::getSetting('time_slots', 0, ''));
                $slots = $slotsJson ? json_decode($slotsJson, true) : [];
                if (is_array($slots) && !empty($slots)) {
                    $startTs = strtotime($start);
                    $endTs   = strtotime($end);
                    $totalPoints = 0.0;

                    // 按天切片，处理跨天与跨午夜的时间段
                    $cur = $startTs;
                    while ($cur < $endTs) {
                        $dayStart = strtotime(date('Y-m-d 00:00:00', $cur));
                        $dayEnd   = $dayStart + 86400;
                        $segStart = max($cur, $dayStart);
                        $segEnd   = min($endTs, $dayEnd);

                        foreach ($slots as $it) {
                            $s = isset($it['start']) ? strval($it['start']) : '00:00';
                            $e = isset($it['end']) ? strval($it['end']) : '24:00';
                            $pph = isset($it['points_per_hour']) ? floatval($it['points_per_hour']) : 0.0;
                            if ($pph <= 0) continue;

                            // 计算当日时段（支持跨午夜）
                            $sh = 0; $sm = 0; $eh = 0; $em = 0;
                            if (strpos($s, ':') !== false) { [$sh,$sm] = array_map('intval', explode(':', $s)); }
                            if (strpos($e, ':') !== false) { [$eh,$em] = array_map('intval', explode(':', $e)); }
                            $slotA = $dayStart + ($sh*3600 + $sm*60);
                            $slotB = ($e === '24:00') ? $dayEnd : ($dayStart + ($eh*3600 + $em*60));

                            $ranges = [];
                            if ($slotB <= $slotA) {
                                // 跨午夜，拆分为 [A,dayEnd) 与 [dayStart,B)
                                $ranges[] = [$slotA, $dayEnd];
                                $ranges[] = [$dayStart, $slotB];
                            } else {
                                $ranges[] = [$slotA, $slotB];
                            }

                            foreach ($ranges as $rg) {
                                [$ra,$rb] = $rg;
                                $ovA = max($segStart, $ra);
                                $ovB = min($segEnd, $rb);
                                if ($ovB > $ovA) {
                                    $mins = ($ovB - $ovA) / 60.0;
                                    $totalPoints += $mins * ($pph / 60.0); // 每小时 pph → 每分钟 pph/60
                                }
                            }
                        }

                        $cur = $segEnd; // 下一天
                    }

                    // 统一取整
                    switch (strtolower($roundingMode)) {
                        case 'ceil':  $score = (int)ceil($totalPoints); break;
                        case 'round': $score = (int)round($totalPoints); break;
                        case 'floor':
                        default:      $score = (int)floor($totalPoints); break;
                    }
                } else {
                    // 2) 未配置时间段时，按“每满 N 分钟 1 分”规则
                    $ratio = $duration / $unitMinutes;
                    switch (strtolower($roundingMode)) {
                        case 'ceil':  $score = (int)ceil($ratio); break;
                        case 'round': $score = (int)round($ratio); break;
                        case 'floor':
                        default:      $score = (int)floor($ratio); break;
                    }
                }

                if ($score < 0) $score = 0;

                // 更新记录为正常并写入积分
                $record->save([
                    'end_time' => $end,
                    'duration_minutes' => $duration,
                    'points' => $score,
                    'status' => 1,
                    'invalid_reason' => null,
                ]);

                // 写积分明细与会员积分
                if ($score > 0) {
                    $memberId = intval($record->getAttr('member_id'));
                    $operator = intval($this->user['id'] ?? 0);

                    PointsModel::mk()->save([
                        'member_id'  => $memberId,
                        'store_id'   => $storeId,
                        'points'     => $score,
                        'type'       => 1, // 获得
                        'source'     => 'checkin',
                        'related_id' => $rid,
                        'status'     => 1,
                        'remark'     => '打卡结算',
                        'operator_id'=> $operator,
                    ]);

                    // 累加会员积分
                    $member = MemberModel::mk()->where(['id' => $memberId])->lock(true)->findOrEmpty();
                    if ($member->isEmpty()) throw new \Exception('会员不存在');
                    $member->save([
                        'total_points'     => intval($member->getAttr('total_points')) + $score,
                        'available_points' => intval($member->getAttr('available_points')) + $score,
                    ]);

                    // 维护会员-门店关系（仅正常记录计数）
                    $rel = MemberStoreModel::mk()->where(['member_id'=>$memberId,'store_id'=>$storeId])->lock(true)->findOrEmpty();
                    if ($rel->isEmpty()) {
                        MemberStoreModel::mk()->save([
                            'member_id' => $memberId,
                            'store_id' => $storeId,
                            'first_time' => $start,
                            'last_time'  => $end,
                            'checkin_count' => 1,
                        ]);
                    } else {
                        $rel->save([
                            'last_time' => $end,
                            'checkin_count' => intval($rel->getAttr('checkin_count')) + 1,
                        ]);
                    }
                }
            });
        }
        $this->success('结束并结算完成！');
    }

    /**
     * 修改状态（正常/无效）
     *
     * 功能说明：
     * 1. 用于手动修正已结算记录的状态
     * 2. 标记为"正常"(status=1)：表示该记录有效，积分有效
     * 3. 标记为"无效"(status=2)：
     *    - 如果记录是"进行中"(status=3)：强制结束签到，不计算积分，清除Redis缓存
     *    - 如果记录已结束：仅标记为无效，不会自动扣除已发放的积分
     *
     * 使用场景：
     * - 用户正在签到中，需要强制结束并标记为无效
     * - 发现某条记录不应该获得积分，可以标记为"无效"（需要手动调整会员积分）
     * - 误标记为无效的记录，可以重新标记为"正常"
     *
     * @auth true
     */
    public function state()
    {
        
        $id = $this->request->post('id', '');
        $newStatus = $this->request->post('status', '');
        if ($id === '') $this->error('缺少记录ID！');
        if (!in_array($newStatus, [1, 2])) $this->error('状态值范围异常！');

        try {
            Db::transaction(function () use ($id, $newStatus) {
                // 读取记录并加锁
                $record = RecordModel::mk()->where(['id' => $id])->lock(true)->findOrEmpty();
                if ($record->isEmpty()) throw new \Exception('记录不存在！');

                $storeId = intval($record->getAttr('store_id'));
                if (!$this->checkStorePermission($storeId)) throw new \Exception('无权限操作该门店的记录！');

                $currentStatus = intval($record->getAttr('status'));
                $memberId = intval($record->getAttr('member_id'));

                // 如果要标记为无效(status=2)，且当前是进行中(status=3)
                if ( $currentStatus === 3) {
                    // 强制结束签到，不计算积分
                    $startTime = $record->getAttr('start_time');
                    if (empty($startTime)) throw new \Exception('记录开始时间异常！');

                    $endTime = date('Y-m-d H:i:s');
                    $duration = max(0, intval((strtotime($endTime) - strtotime($startTime)) / 60));

                    // 更新记录：结束签到，标记为无效，积分为0
                    $record->save([
                        'end_time' => $endTime,
                        'duration_minutes' => $duration,
                        'points' => 0,
                        'status' => 2,
                        'invalid_reason' => '手动标记无效',
                    ]);

                    // 清除Redis缓存（关键！）
                    $this->clearCheckinRedisCache($memberId);

                } else {
                    // 其他情况：仅修改状态
                    $record->save(['status' => $newStatus]);
                }
            });

            $this->success('状态修改成功！');

        } catch (\think\exception\HttpResponseException $e) {
            throw $e;
        } catch (\Exception $e) {
            $this->error('操作失败：' . $e->getMessage());
        }
    }

    /**
     * 清除签到相关的Redis缓存
     * @param int $memberId 会员ID
     */
    private function clearCheckinRedisCache(int $memberId)
    {
        try {
            // 获取Redis连接
            $redis = new \Redis();
            $redisConfig = [
                'host' => env('REDIS_HOST', '127.0.0.1'),
                'port' => env('REDIS_PORT', 6379),
                'password' => env('REDIS_PASSWORD', ''),
                'database' => env('REDIS_DATABASE', 0),
            ];

            $redis->connect($redisConfig['host'], $redisConfig['port'], 2);
            if (!empty($redisConfig['password'])) {
                $redis->auth($redisConfig['password']);
            }
            $redis->select($redisConfig['database']);

            // 删除进行中的签到记录缓存（最关键！）
            $ongoingKey = "checkin:ongoing:{$memberId}";
            $redis->del($ongoingKey);

            // 删除会员信息缓存
            $memberKey = "member:{$memberId}";
            $redis->del($memberKey);

            $redis->close();

            trace("清除Redis缓存成功 - member_id: {$memberId}, keys: {$ongoingKey}, {$memberKey}", 'info');

        } catch (\Throwable $e) {
            // Redis操作失败不影响业务流程，只记录日志
            trace("清除Redis缓存失败：" . $e->getMessage(), 'warning');
        }
    }

    /**
     * 删除记录
     * @auth true
     */
    public function remove()
    {
        RecordModel::mDelete();
    }

    /**
     * 重新计算积分（不更新数据库，仅返回计算结果）
     * @auth true
     */
    public function recalculate()
    {
        $id = intval($this->request->post('id', 0));
        if ($id <= 0) $this->error('缺少记录ID！');

        try {
            // 读取记录
            $record = RecordModel::mk()->where(['id' => $id])->findOrEmpty();
            if ($record->isEmpty()) $this->error('记录不存在！');

            $storeId = intval($record->getAttr('store_id'));
            if (!$this->checkStorePermission($storeId)) $this->error('无权限操作该门店的记录！');

            $status = intval($record->getAttr('status'));
            if ($status !== 1) $this->error('只能重新计算状态为"正常"的记录！');

            $startTime = $record->getAttr('start_time');
            $endTime = $record->getAttr('end_time');
            $durationMinutes = intval($record->getAttr('duration_minutes'));
            $oldPoints = intval($record->getAttr('points'));

            if (empty($startTime) || empty($endTime)) {
                $this->error('记录时间数据异常！');
            }

            // 获取系统配置
            $config = SettingModel::getConfig(0);
            $unitMinutes = intval($config->unit_minutes ?? 10);
            $unitPoints = intval($config->unit_points ?? 1);

            // 获取时间段配置，确保是数组
            $timeSlots = $config->time_slots;
            if (!is_array($timeSlots)) {
                $timeSlots = [];
            }

            if ($unitMinutes <= 0) $unitMinutes = 10;
            if ($unitPoints <= 0) $unitPoints = 1;

            // 调试日志
            trace('重新计算积分 - 配置信息：unitMinutes=' . $unitMinutes . ', unitPoints=' . $unitPoints . ', timeSlots=' . json_encode($timeSlots, JSON_UNESCAPED_UNICODE), 'info');
            trace('重新计算积分 - 记录信息：startTime=' . $startTime . ', endTime=' . $endTime . ', durationMinutes=' . $durationMinutes, 'info');

            // 使用新的积分计算逻辑，获取详细信息
            $result = $this->calculatePointsNew($durationMinutes, $startTime, $endTime, $unitMinutes, $unitPoints, $timeSlots, true);

            $newPoints = $result['points'];
            $details = $result['details'];
            $diff = $newPoints - $oldPoints;

            $this->success('计算完成', [
                'old_points' => $oldPoints,
                'new_points' => $newPoints,
                'diff' => $diff,
                'duration_minutes' => $durationMinutes,
                'start_time' => $startTime,
                'end_time' => $endTime,
                'unit_minutes' => $unitMinutes,
                'details' => $details
            ]);

        } catch (\think\exception\HttpResponseException $e) {
            // HttpResponseException 是正常的响应异常，直接抛出
            throw $e;
        } catch (\Exception $e) {
            // 记录详细错误信息到日志
            $errorMsg = $e->getMessage();
            $errorFile = $e->getFile();
            $errorLine = $e->getLine();
            $errorTrace = $e->getTraceAsString();

            trace('积分重新计算失败：' . $errorMsg . ' | 文件：' . $errorFile . ' | 行号：' . $errorLine . "\n堆栈：\n" . $errorTrace, 'error');

            // 返回详细错误信息（开发环境）
            $this->error('计算失败：' . $errorMsg . ' (文件：' . basename($errorFile) . ':' . $errorLine . ')');
        }
    }

    /**
     * 确认更新积分
     * @auth true
     */
    public function updatePoints()
    {
        $id = intval($this->request->post('id', 0));
        $oldPoints = intval($this->request->post('old_points', 0));
        $newPoints = intval($this->request->post('new_points', 0));
        $diff = intval($this->request->post('diff', 0));

        if ($id <= 0) $this->error('缺少记录ID！');
        if ($diff === 0) $this->error('积分无变化，无需更新！');

        try {
            Db::transaction(function () use ($id, $oldPoints, $newPoints, $diff) {
                // 1. 读取记录并加锁
                $record = RecordModel::mk()->where(['id' => $id])->lock(true)->findOrEmpty();
                if ($record->isEmpty()) throw new \Exception('记录不存在！');

                $storeId = intval($record->getAttr('store_id'));
                if (!$this->checkStorePermission($storeId)) throw new \Exception('无权限操作该门店的记录！');

                $status = intval($record->getAttr('status'));
                if ($status !== 1) throw new \Exception('只能更新状态为"正常"的记录！');

                $currentPoints = intval($record->getAttr('points'));
                if ($currentPoints !== $oldPoints) {
                    throw new \Exception('记录积分已变化，请重新计算！');
                }

                $memberId = intval($record->getAttr('member_id'));
                $operatorId = intval($this->user['id'] ?? 0);

                // 2. 更新签到记录的积分
                $record->save(['points' => $newPoints]);

                // 3. 更新会员积分
                $member = MemberModel::mk()->where(['id' => $memberId])->lock(true)->findOrEmpty();
                if ($member->isEmpty()) throw new \Exception('会员不存在！');

                $member->save([
                    'total_points' => intval($member->getAttr('total_points')) + $diff,
                    'available_points' => intval($member->getAttr('available_points')) + $diff,
                ]);

                // 4. 插入积分调整记录
                $type = $diff > 0 ? 1 : 2; // 1=获得, 2=消费（扣减）
                $remark = $diff > 0
                    ? "积分重新计算调整（原{$oldPoints}分 → 新{$newPoints}分，增加{$diff}分）"
                    : "积分重新计算调整（原{$oldPoints}分 → 新{$newPoints}分，扣减" . abs($diff) . "分）";

                PointsModel::mk()->save([
                    'member_id' => $memberId,
                    'store_id' => $storeId,
                    'points' => abs($diff),
                    'type' => $type,
                    'source' => 'adjust',
                    'related_id' => $id,
                    'status' => 1,
                    'remark' => $remark,
                    'operator_id' => $operatorId,
                ]);
            });

            $this->success('积分更新成功！');

        } catch (\think\exception\HttpResponseException $e) {
            // HttpResponseException 是正常的响应异常，直接抛出
            throw $e;
        } catch (\Exception $e) {
            $this->error('更新失败：' . $e->getMessage());
        }
    }

    /**
     * 一键批量重新计算并更新积分
     * @auth true
     */
    public function batchRecalculate()
    {
        $startTime = $this->request->post('start_time', '');
        $endTime = $this->request->post('end_time', '');

        if (empty($startTime) || empty($endTime)) {
            $this->error('请选择开始时间和结束时间！');
        }

        try {
            // 获取系统配置
            $config = SettingModel::getConfig(0);
            $unitMinutes = intval($config->unit_minutes ?? 10);
            $unitPoints = intval($config->unit_points ?? 1);
            $timeSlots = $config->time_slots;
            if (!is_array($timeSlots)) {
                $timeSlots = [];
            }

            if ($unitMinutes <= 0) $unitMinutes = 10;
            if ($unitPoints <= 0) $unitPoints = 1;

            // 查询符合条件的记录（status=1 正常记录）
            $query = RecordModel::mk()->where('status', 1)
                ->where('start_time', '>=', $startTime)
                ->where('start_time', '<=', $endTime)
                ->where('end_time', 'not null');

            // 数据权限：非总账号只能操作本店记录
            if (!$this->isMaster) {
                $relTable = MemberStoreModel::mk()->getTable();
                $sid = $this->storeId;
                $query->where(function ($q) use ($relTable, $sid) {
                    $q->where(['store_id' => $sid])
                      ->whereOrRaw("member_id IN (SELECT member_id FROM {$relTable} WHERE store_id = :sid)", ['sid' => $sid]);
                });
            }

            $records = $query->select();

            if ($records->isEmpty()) {
                $this->error('没有找到符合条件的记录！');
            }

            $totalCount = count($records);
            $successCount = 0;
            $errorCount = 0;
            $noChangeCount = 0;
            $totalDiff = 0;
            $operatorId = intval($this->user['id'] ?? 0);

            // 逐条计算并更新
            foreach ($records as $record) {
                try {
                    $recordId = intval($record->getAttr('id'));
                    $recordStartTime = $record->getAttr('start_time');
                    $recordEndTime = $record->getAttr('end_time');
                    $durationMinutes = intval($record->getAttr('duration_minutes'));
                    $oldPoints = intval($record->getAttr('points'));
                    $storeId = intval($record->getAttr('store_id'));
                    $memberId = intval($record->getAttr('member_id'));

                    // 重新计算积分
                    $newPoints = $this->calculatePointsNew($durationMinutes, $recordStartTime, $recordEndTime, $unitMinutes, $unitPoints, $timeSlots);
                    $diff = $newPoints - $oldPoints;

                    // 如果积分无变化，跳过
                    if ($diff === 0) {
                        $noChangeCount++;
                        continue;
                    }

                    // 使用事务更新
                    Db::transaction(function () use ($recordId, $memberId, $storeId, $oldPoints, $newPoints, $diff, $operatorId) {
                        // 1. 更新签到记录的积分
                        RecordModel::mk()->where(['id' => $recordId])->update(['points' => $newPoints]);

                        // 2. 更新会员积分
                        MemberModel::mk()->where(['id' => $memberId])->inc('total_points', $diff)->inc('available_points', $diff)->update();

                        // 3. 插入积分调整记录
                        $type = $diff > 0 ? 1 : 2; // 1=获得, 2=消费（扣减）
                        $remark = $diff > 0
                            ? "批量积分重新计算调整（原{$oldPoints}分 → 新{$newPoints}分，增加{$diff}分）"
                            : "批量积分重新计算调整（原{$oldPoints}分 → 新{$newPoints}分，扣减" . abs($diff) . "分）";

                        PointsModel::mk()->save([
                            'member_id' => $memberId,
                            'store_id' => $storeId,
                            'points' => abs($diff),
                            'type' => $type,
                            'source' => 'adjust',
                            'related_id' => $recordId,
                            'status' => 1,
                            'remark' => $remark,
                            'operator_id' => $operatorId,
                        ]);
                    });

                    $totalDiff += $diff;
                    $successCount++;

                } catch (\Exception $e) {
                    $errorCount++;
                    trace('批量重新计算积分失败 - 记录ID：' . ($recordId ?? 'unknown') . '，错误：' . $e->getMessage(), 'error');
                }
            }

            // 构建结果消息
            $message = "批量更新完成！";
            $message .= "\n扫描记录：{$totalCount} 条";
            $message .= "\n成功更新：{$successCount} 条";
            if ($noChangeCount > 0) {
                $message .= "\n无需更新：{$noChangeCount} 条";
            }
            if ($errorCount > 0) {
                $message .= "\n更新失败：{$errorCount} 条";
            }
            $message .= "\n总积分变化：" . ($totalDiff > 0 ? '+' : '') . $totalDiff . " 分";

            $this->success($message, [
                'total_count' => $totalCount,
                'success_count' => $successCount,
                'no_change_count' => $noChangeCount,
                'error_count' => $errorCount,
                'total_diff' => $totalDiff
            ]);

        } catch (\think\exception\HttpResponseException $e) {
            throw $e;
        } catch (\Exception $e) {
            $this->error('批量计算失败：' . $e->getMessage());
        }
    }

    /**
     * 新的积分计算方法（按时间段拆分计算）
     *
     * @param int $durationMinutes 签到时长（分钟）
     * @param string $startTime 开始时间
     * @param string $endTime 结束时间
     * @param int $unitMinutes 单位分钟数
     * @param int $unitPoints 通用积分（每单位记多少分）
     * @param array $timeSlots 时间段配置数组
     * @param bool $returnDetail 是否返回详细信息
     * @return int|array 积分或详细信息数组
     */
    private function calculatePointsNew(int $durationMinutes, string $startTime, string $endTime, int $unitMinutes, int $unitPoints, $timeSlots, bool $returnDetail = false)
    {
        // 如果有时间段配置，使用时间段规则
        if (is_array($timeSlots) && count($timeSlots) > 0) {
            return $this->calculatePointsByTimeSlots($durationMinutes, $startTime, $endTime, $timeSlots, $unitMinutes, $unitPoints, $returnDetail);
        }

        // 使用基础规则：floor(时长 / 单位分钟) × 单位积分
        $units = intval(floor($durationMinutes / $unitMinutes));
        $points = $units * $unitPoints;

        if ($returnDetail) {
            return [
                'points' => $points,
                'details' => [
                    [
                        'segment' => '基础积分',
                        'minutes' => $durationMinutes,
                        'units' => $units,
                        'points_per_unit' => $unitPoints,
                        'points' => $points
                    ]
                ]
            ];
        }

        return $points;
    }

    /**
     * 按时间段计算积分（改进版：按时间段拆分计算）
     *
     * @param int $durationMinutes 签到时长（分钟）
     * @param string $startTime 开始时间
     * @param string $endTime 结束时间
     * @param array $timeSlots 时间段配置
     * @param int $unitMinutes 单位分钟数
     * @param int $unitPoints 通用积分（每单位记多少分）
     * @param bool $returnDetail 是否返回详细信息
     * @return int|array 积分或详细信息数组
     */
    private function calculatePointsByTimeSlots(int $durationMinutes, string $startTime, string $endTime, array $timeSlots, int $unitMinutes, int $unitPoints, bool $returnDetail = false)
    {
        try {
            if ($unitMinutes <= 0) $unitMinutes = 10;
            if ($unitPoints <= 0) $unitPoints = 1;

            // 将开始和结束时间转换为时间戳
            $startTimestamp = strtotime($startTime);
            $endTimestamp = strtotime($endTime);

            trace('calculatePointsByTimeSlots - 参数：durationMinutes=' . $durationMinutes . ', startTime=' . $startTime . ', endTime=' . $endTime . ', timeSlots数量=' . count($timeSlots), 'info');

        // 解析时间段配置，转换为分钟数（从当天00:00开始）
        $parsedSlots = [];
        foreach ($timeSlots as $slot) {
            $slotStart = $slot['start'] ?? '';
            $slotEnd = $slot['end'] ?? '';

            if (empty($slotStart) || empty($slotEnd)) continue;

            // 解析时间段的开始和结束时间
            $startParts = explode(':', $slotStart);
            $endParts = explode(':', $slotEnd);

            if (count($startParts) < 2 || count($endParts) < 2) {
                trace("时间段格式错误：start={$slotStart}, end={$slotEnd}", 'error');
                continue;
            }

            $sh = intval($startParts[0]);
            $sm = intval($startParts[1]);
            $eh = intval($endParts[0]);
            $em = intval($endParts[1]);

            $slotStartMinutes = $sh * 60 + $sm;
            $slotEndMinutes = $eh * 60 + $em;
            $pointsPerUnit = intval($slot['points_per_hour'] ?? 1);

            $parsedSlots[] = [
                'start' => $slotStartMinutes,
                'end' => $slotEndMinutes,
                'points_per_unit' => $pointsPerUnit,
                'label' => "{$slotStart}-{$slotEnd}"
            ];
        }

        // 按开始时间排序时间段
        usort($parsedSlots, function($a, $b) {
            return $a['start'] - $b['start'];
        });

        // 将签到时长按分钟拆分，逐分钟计算所属时间段
        $totalPoints = 0;
        $currentTimestamp = $startTimestamp;

        // 用于统计每个时间段的分钟数
        $segmentStats = [];

        while ($currentTimestamp < $endTimestamp) {
            // 获取当前时间点的小时和分钟
            $currentHour = intval(date('H', $currentTimestamp));
            $currentMinute = intval(date('i', $currentTimestamp));
            $currentTimeValue = $currentHour * 60 + $currentMinute;

            // 查找当前时间点所属的时间段
            $matchedSlot = null;
            foreach ($parsedSlots as $slot) {
                // 时间段边界：左闭右开 [start, end)
                if ($currentTimeValue >= $slot['start'] && $currentTimeValue < $slot['end']) {
                    $matchedSlot = $slot;
                    break;
                }
            }

            // 确定当前时间段的积分规则
            if ($matchedSlot !== null) {
                $segmentKey = $matchedSlot['label'];
                $pointsPerUnit = $matchedSlot['points_per_unit'];
            } else {
                $segmentKey = 'base';
                $pointsPerUnit = $unitPoints;
            }

            // 累计该时间段的分钟数
            if (!isset($segmentStats[$segmentKey])) {
                $segmentStats[$segmentKey] = [
                    'minutes' => 0,
                    'points_per_unit' => $pointsPerUnit
                ];
            }
            $segmentStats[$segmentKey]['minutes']++;

            // 移动到下一分钟
            $currentTimestamp += 60;
        }

        // 根据每个时间段的分钟数计算积分
        $details = [];
        foreach ($segmentStats as $segmentKey => $stats) {
            $minutes = $stats['minutes'];
            $pointsPerUnit = $stats['points_per_unit'];

            // 计算单位数（向下取整）
            $units = intval(floor($minutes / $unitMinutes));
            $segmentPoints = $units * $pointsPerUnit;

            $totalPoints += $segmentPoints;

            // 记录详细信息
            $details[] = [
                'segment' => $segmentKey,
                'minutes' => $minutes,
                'units' => $units,
                'points_per_unit' => $pointsPerUnit,
                'points' => $segmentPoints
            ];
        }

        if ($returnDetail) {
            return [
                'points' => $totalPoints,
                'details' => $details
            ];
        }

        return $totalPoints;

        } catch (\Exception $e) {
            trace('calculatePointsByTimeSlots 错误：' . $e->getMessage() . ' | 行号：' . $e->getLine(), 'error');
            throw $e;
        }
    }
}
